package com.compress4j.arithmetic.ppm;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.compress4j.arithmetic.ArithmeticDecoder;
import com.compress4j.arithmetic.ArithmeticEncoder;
/**
 * TODO: extract interface
 * 
 * @author b.pierre
 *
 * @param <T>
 */
public class PPMTree<T> {
	
	//root context : the one of size 0
	private PPMNode<T> root;

	//will not keep context longuer than that
	private int PPMhigh;
	
	//will not keep context longuer than that if previous is deterministic
	private int PPMlow;

	//contexts in use by length
	private PPMNode<T>[] runningContexts;

	//keep inside to avoid re-creation
	protected Set<T> excluded;

	//keep inside to avoid re-creation
	protected RangeBean interval = new RangeBean();

	@SuppressWarnings("unchecked")
	public PPMTree(int ppmHigh,int ppmLow,T[] model,Set<T> excluded) {
		root = new PPMNode<T>(null,model.length+1);
		for(T item : model){
			root.addWeight(item, 1);
		}
		PPMhigh = ppmHigh;
		PPMlow = ppmLow;
		this.excluded = excluded;
		runningContexts = new PPMNode[PPMhigh+1];
		runningContexts[0] = root;
	}
	/**
	 * add weight of used item to all contexts
	 * and reposition contexts of each length
	 * @param newItem
	 */
	public void handleUsedItem(T newItem){
		// add weight on each context
		// here is the magic of ppm, the method addWeight return the future node to use
		for(int i=runningContexts.length-2;i>=0;i--){
			PPMNode<T> node = runningContexts[i];
			if(node==null){
				runningContexts[i+1] = null;
			}else{
				runningContexts[i+1] = node.addWeight(newItem, 1);
			}
		}
		//add the root at the beginning
		runningContexts[0] = root;
		
		//between PPMLow and PPMHight we keep only items that have several subnodes
		//it allows to save a lot of memory, for a very acceptable compression loss
		for(int i=PPMlow, max=PPMhigh+1;i<max;i++){
			PPMNode<T> node = runningContexts[i];
			if(node==null) break;
			if(node.onePossibility()){
				i++;
				for(;i<max;i++){
					runningContexts[i] = null;
				}
			}
		}
	}
/**
 * 
 * @return the list of context to use
 * but if PPMlow is not 0, and if all my context have just one possibility the longuer one is the worst (contrary to normal case)
 */
	private List<PPMNode<T>> getCurrentContexts(){
		LinkedList<PPMNode<T>> list = new LinkedList<PPMNode<T>>();
		for(PPMNode<T> crawler : runningContexts){
			if(crawler==null) break;
			if(crawler.getFirstChild() == null) break;
			list.add(0,crawler);
			if(crawler.onePossibility()) break;
		}
		return list;
	}
/**
 * write the item
 * @param coder
 * @param o
 * @throws IOException
 */
	public void write(ArithmeticEncoder coder, T o) throws IOException{
		excluded.clear();
		for(PPMNode<T> ctx : getCurrentContexts()){
			boolean done = ctx.fillRange(o,excluded,interval);
			
			coder.encode(interval.getStart(), interval.getEnd(), interval.getMax());
    		
			if(done){
    			break;
			}else{
    			ctx.excludeAllChild(excluded);
			}
    	}
	}
/**
 * read the next item	
 * @param decoder
 * @return
 * @throws IOException
 */
	public T read(ArithmeticDecoder decoder) throws IOException{
		excluded.clear();
		for(PPMNode<T> ctx :  getCurrentContexts()){
    		
			int total = ctx.getTotal(excluded);
			int readed = decoder.read(total);
			T result = ctx.getItem(readed,total,excluded,interval);
			decoder.hasDecoded(interval.getStart(), interval.getEnd(), interval.getMax());
    		
    		if(result!=null){
    			return result;
			}else{
				ctx.excludeAllChild(excluded);
			}
    	}
    	return null;
    }
	
}
